﻿namespace Sai.Test
{
    using Microsoft.CSharp;
    using Sai.Core;
    using System;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;

    public sealed class OnTheFlyAssembly : IDisposable
	{
		private const string DllExtension = ".dll";
		private const string PdbExtension = ".pdb";

		public OnTheFlyAssembly()
			: this(Path.GetRandomFileName()) 
		{ 
		}

		public OnTheFlyAssembly(string assemblyFileName)
		{
			this.FileName = Path.Combine(this.Folder, assemblyFileName + DllExtension);

			this.AssemblyName = new AssemblyName();
			this.AssemblyName.Name = Path.GetFileNameWithoutExtension(this.FileName);
			this.AssemblyName.CodeBase = Path.GetDirectoryName(Path.GetFullPath(this.FileName));
		}

		public ProductAssembly ProductAssembly
		{
			get
			{
				return new ProductAssembly(this.FileName);
			}
		}

		private string Folder
		{
			get
			{
				var codeBase = Assembly.GetExecutingAssembly().CodeBase;
				var localPath = new Uri(codeBase).LocalPath;

				return Path.GetDirectoryName(localPath);
			}
		}

		private AssemblyName AssemblyName { get; set; }

		private string FileName { get; set; }
		
		public void Dispose()
		{
			if (File.Exists(this.FileName))
			{
				File.Delete(this.FileName);
			}

			string path = Path.GetDirectoryName(this.FileName);
			string debugDBName = Path.GetFileNameWithoutExtension(this.FileName);
			debugDBName = Path.Combine(path, debugDBName + PdbExtension);

			if (File.Exists(debugDBName))
			{
				File.Delete(debugDBName);
			}
		}

		public void Compile(string code)
		{
			CompilerParameters parameters = new CompilerParameters()
				{
					OutputAssembly = this.FileName,
					IncludeDebugInformation = true
				};

			parameters.ReferencedAssemblies.Add("System.dll");

			var compilerOptions = new Dictionary<string, string> { { "CompilerVersion", "v4.0" } };

			var provider = new CSharpCodeProvider(compilerOptions);
			var results = provider.CompileAssemblyFromSource(parameters, code);

			if (results.Errors.Count != 0)
			{
				var errors = new List<string>();

				foreach (CompilerError compilerError in results.Errors)
				{
					errors.Add(this.BuildErrorMessage(compilerError));
				}

				throw new InvalidOperationException("Compilation Failed:\n" + string.Join(Environment.NewLine, errors.ToArray()));
			}
		}

		private string BuildErrorMessage(CompilerError compilerError)
		{
			return string.Format(
				"{0}({1},{2}): error {3}: {4}",
				compilerError.FileName,
				compilerError.Line,
				compilerError.Column,
				compilerError.ErrorNumber,
				compilerError.ErrorText);
		}
	}
}
